<?php
/* This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details. */

// Written by Shesek, http://www.shesek.info/php/toodledo-api
require_once 'XML/Unserializer.php';

class ToodledoConstants
{
	const KEY_ID = 'id';
	const KEY_TITLE = 'title';
	const KEY_STARTDATE = 'startdate';
	const KEY_STARTTIME = 'starttime';
	const KEY_LENGTH = 'length';
	const KEY_NOTE = 'note';
	const KEY_MODIFIED = 'modified';
	const KEY_UNIX = 'unix';
	const KEY_COMPLETED = 'completed';
	const METHOD_ADDTASK = 'addTask';
	const METHOD_GETTASKS = 'getTasks';
	const METHOD_EDITTASK = 'editTask';
	const VALUE_ERROR_LOGIN_ERROR = 'key did not validate';
}

class ToodledoAnalyze
{
	static public function outAnalyze($data)
	{
		if (class_exists('analyzer')) {
			analyzer::saveLog('Toodledo', date(DATE_ATOM) . "....\n" . $data . "\n......\n");
		}
	}
}

class Toodledo {
	public $exeptionMessage = '';
	const EXERROR_EMPTY_RESPONCE = 'Empty response';
	const EXERROR_INVALID_RESPONCE = 'Invalid response while getting token';

	const CACHEDIR = '.';
	const APIURL = 'http://api.toodledo.com/api.php?';
	const TOKENCACHETIME = 13800; // 3 hours and 50 minutes

	const XML=1;
	const RAW=2;
	const ARR=3;
	const DEFAULT_RETURN=1;

	protected $key;
	protected $_cacheDir;

	public function __construct($userid, $password, $appid = null){
		$this->_cacheDir = dirname(__FILE__);
		$token = $this->getToken($userid, $appid);
		$this->key = md5(md5($password) . $token . $userid);
	}

	public function request($method, $params=array(), $output=self::DEFAULT_RETURN){
		/*ANALYZE*/ToodledoAnalyze::outAnalyze('start Toodledo::request');
		if (!empty($this->key)) $params['key'] = $this->key;
		$url = self::APIURL . 'method='.$method . (!empty($params) ? ';' . http_build_query($params, '', ';') : '');
		/*ANALYZE*/ToodledoAnalyze::outAnalyze($url);
		$string = file_get_contents($url);
		if (empty($string)) {
			$this->exceptionMessage=EXERROR_EMPTY_RESPONCE;
			/*ANALYZE*/ToodledoAnalyze::outAnalyze($this->exeptionMessage);
			return FALSE;
		}
		if ($output === self::RAW){
			/*ANALYZE*/ToodledoAnalyze::outAnalyze($string);
			return $string;
		}
		// Wrap with <response> so outer-most wrapper is accesible with $xml->foo
		if (substr($string,0,2) === '<?' && ($pos=strpos($string,'?>')) !== false && $pos < strpos($string, '<', 1))
			$string = substr_replace($string, '<response>', $pos+2, 0) . '</response>';
		else
			$string = '<response>' . $string . '</response>';
		$xml = new SimpleXMLElement($string);
		$error = $xml->error;
		if (!empty($error)) {
			$this->exceptionMessage=(string)$error;
			/*ANALYZE*/ToodledoAnalyze::outAnalyze($this->exeptionMessage);
			return FALSE;
		}
		if ($output === self::ARR)  {
			/*ANALYZE*/ToodledoAnalyze::outAnalyze(self::simplexml2array($xml));
			return self::simplexml2array($xml);
		}
		/*ANALYZE*/ToodledoAnalyze::outAnalyze($xml);
		return $xml;
	}

	public function __call($method, $args) {
		// Could've array_unshift($args, $method); return call_user_func_array(array($this,'request'),$args);, but its slower
		return $this->request($method, (isset($args[0]) ? $args[0] : array()), (isset($args[1]) ? $args[1] : self::DEFAULT_RETURN));
	}


	protected function getToken($userid, $appid=null) {
		/*ANALYZE*/ToodledoAnalyze::outAnalyze('start Toodledo::getToken');
		$cache_path = $this->_cacheDir . '/toodledo.token.' . $userid;
		if (file_exists($cache_path) && filemtime($cache_path) >= time() - self::TOKENCACHETIME){ // 3.5 hours
			/*ANALYZE*/ToodledoAnalyze::outAnalyze(file_get_contents($cache_path));
			/*ANALYZE*/ToodledoAnalyze::outAnalyze('AAAAAA');
			return file_get_contents($cache_path);
		}
		$params = array('userid'=>$userid);
		if (!empty($appid)) $params['appid'] = $appid;
		$response = $this->request('getToken', $params, self::XML);
		/*ANALYZE*/ToodledoAnalyze::outAnalyze(print_r($response,true));
		$token = $response->token;
		if (empty($token)) {
			$this->exceptionMessage=EXERROR_INVALID_RESPONCE;
			/*ANALYZE*/ToodledoAnalyze::outAnalyze($this->exeptionMessage);
			return FALSE;
		}
		$token = (string)$token;
		/*ANALYZE*/ToodledoAnalyze::outAnalyze($token);
		file_put_contents($cache_path, $token);
		return $token;
	}

	public static function simplexml2array($xml) {
	   if (get_class($xml) == 'SimpleXMLElement') {
	       $attributes = $xml->attributes();
	       foreach($attributes as $k=>$v) {
		   if ($v) $a[$k] = (string) $v;
	       }
	       $x = $xml;
	       $xml = get_object_vars($xml);
	   }
	   if (is_array($xml)) {
	       if (count($xml) == 0) return (string) $x; // for CDATA
	       foreach($xml as $key=>$value) {
		   $r[$key] = self::simplexml2array($value);
	       }
	       if (isset($a)) $r['@'] = $a;    // Attributes
	       return $r;
	   }
	   return (string) $xml;
	}

	static public function xml2array2($xml)
	{
		$sx = simplexml_load_string($xml);
		$arrResult = json_decode(json_encode($sx), true);
		return$arrResult;
	}

	static public function xml2array($xml)
	{
		$uns = new XML_Unserializer(
				array('parseAttributes' => true,
						'forceEnum'=> array('task')));
		$status = $uns->unserialize($xml);
		$result = $uns->getUnserializedData();
		return $result;
	}

	/**
	 * UNIXタイムスタンプからToodle用の時刻を取得する
	 * xamples: 12:34 am, 4:56 pm
	 * @param unknown $unixTime
	 * @return multitype:
	 */
	static public function divideUnixTime2Time($unixTime)
	{
		$sResult = '';
		$aryResult = array();
		$sResult = date('g:i a', $unixTime);
		return $sResult;
	}

	/**
	 * Toodole形式の日付と時刻をUNIXタイムスタンプ形式に変換する
	 * @param unknown $Date
	 * @param unknown $Time
	 * @return number
	 */
	static public function unixTimeFromDateAndTime($Date, $Time)
	{
		$uTimeResult = 0;
		$uTimeResult = strtotime($Date . ' ' . $Time);
		return $uTimeResult;
	}

	/**
	 * げった
	 *
	 * @param unknown $name
	 * @return unknown
	 */
	public function __get($name) {
		$method = 'get_' .  $name;
		if (method_exists ( $this, $method )) {
			return call_user_func ( array (
					&$this,
					$method
			) );
		} else {
			trigger_error( 'Property ' . $name . '  does not exist');
		}
	}
}

/**
 * 単体テストコードブロック
 */
if (PATH_SEPARATOR == ';') {
include_once 'simpletest/unit_tester.php';
include_once 'simpletest/reporter.php';

if (class_exists('UnitTestCase')) {
class ToodledoTest extends UnitTestCase
{
	protected $_toodledo;

	public function setUp()
	{
		$aryPassword = file(dirname(__FILE__).'/password.txt',FILE_IGNORE_NEW_LINES);
		$userid = $aryPassword[0];
		$password = $aryPassword[1];
		$appid = $aryPassword[2];
		$this->_toodledo = new Toodledo($userid, $password, $appid);

	}

	public function test9000()
	{
		echo (string)$this->_toodledo->me;
	}

	public function test1400()
	{
		//UNIXタイムスタンプ変換
		$uTime = Toodledo::unixTimeFromDateAndTime('2014-01-31', '');
		echo date('Y.m.d H:i:s', $uTime) . "\n";
	}

	public function test1100()
	{
		$updated =mktime(5,43,23,8,28,2011);
		$updated = '2013-12-28 05:43:22';
		$updated = 1388177002;
		$result = $this->_toodledo->request('getTasks',array('end'=>2,'modafter'=>$updated,ToodledoConstants::KEY_UNIX=>'1'), Toodledo::RAW);
//		$objXml = new Lib_Xml()
		print_r($result);
		echo "====hash====\n";
		print_r(Toodledo::xml2array($result));
		print_r(Toodledo::xml2array2($result));
	}

	public function test1200()
	{
		//イベント追加
		// title=new years;priority=1;repeat=0;length=30;duedate=2010-01-01
		$taskInfo = array(
				'title' => 'たんたいてすとによる',
				'note' => 'めもめも',
				'startdate' => '2013-12-30',
				'starttime' => '1:34 pm',
				'length' => '90',
		);
		$result = $this->_toodledo->request('addTask', $taskInfo, Toodledo::RAW);
		echo $result . "\n";
		//return;
		$arr = Toodledo::xml2array($result);
		print_r($arr);
		echo "\n***\n";
	}

	public function test1210()
	{
		/**
		 * 開始日時をUNIXタイムスタンプで指定する
		 */
		$taskInfo = array(
				'title' => 'UNIXタイムスタンプ',
				'note' => 'めもめも',
				'startdate' => time(),
				'starttime' => Toodledo::divideUnixTime2Time(time()),
		);
		$result = $this->_toodledo->request('addTask', $taskInfo, Toodledo::RAW);
		print_r($result);
	}

	public function test1300()
	{
		//イベント追加
		// title=new years;priority=1;repeat=0;length=30;duedate=2010-01-01
		$taskInfo = array(
				'id' => '214034545',
				'title' => 'たんたいてすとによるを変更',
				'note' => 'めもめもも変更',
				'startdate' => '2013-12-29',
				'starttime' => '12:34 pm',
				'duedate' => '2013-12-30',
				'duetime' => '10:56 pm'
		);
		$result = $this->_toodledo->request('editTask', $taskInfo, Toodledo::RAW);
		print_r($result);
	}
}
}
}